using System;
using System.Xml;
using System.Text;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Instance;
using System.Xml.Linq;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Instance
{
	/// <summary>
    /// Facts can be simple, in which case their values must be expressed as simple content - item.
    /// Facts can be compound, in which case their value is made up from other simple and/or compound facts Tuple.
	/// </summary>
    public class Fact
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private InstanceDocument parentInstance;

        /// <returns> The instance of this item. </returns>
        public InstanceDocument ParentInstance
        {
            get { return parentInstance; }
            set { parentInstance = value; }
        }

        private XElement factElement;

        /// <summary>
        /// Gets or sets the fact element.
        /// </summary>
        /// <value>
        /// The fact element.
        /// </value>
        /// <returns> The fact element. </returns>
        public XElement FactElement
        {
            get { return factElement; }
            set { factElement = value; }
        } 

        private Concept conceptItem;

        /// <summary>
        /// The schema element that defines this item.
        /// </summary>
        public Concept Concept
        {
            get
            {
                return conceptItem;
            }
            protected set
            {
                conceptItem = value;
            }
        }

        /// <summary>
        /// The dts.
        /// </summary>
        protected DTS dts;

        /// <summary>
		/// The name of the fact.
		/// </summary>
		public string Name
		{
			get;
			private set;
		}        

        /// <summary>
        /// The namespace for this item.
        /// </summary>
        public string Namespace
        {
            get
            {
                return factElement.Name.NamespaceName;
            }
            private set { ;}
        }
        // 
        // 
        //

        /// <summary>
        /// XBRL 2.1 Specification - 4.9   Tuples
        /// Tuples might need to be referenced from elsewhere (such as from a footnote). Therefore, all tuple
        /// declarations in taxonomy schemas SHOULD (but are not required to) include a declaration of an optional
        /// local attribute with name id of type xsd:ID. Authors of extension taxonomies SHOULD NOT
        /// prohibit the id attribute, if one exists, when restricting tuple datatypes.
        /// NOTE: If the taxonomy author fails to define or prohibits an id attribute for a tuple then that tuple
        /// will not be referenceable by shorthand xpointers.
        /// The ID of this fact.
        /// </summary>
        public string Id
        {
            get;
            private set;
        }

        internal Fact(InstanceDocument parentInstance, XElement factElement, DTS dtsFact)
        {
            dts = dtsFact;
            this.parentInstance = parentInstance;
            this.factElement = factElement;
            this.Name = factElement.Name.LocalName;
            this.Namespace = factElement.Name.NamespaceName;
            this.Id = (string)factElement.Attribute("id") ?? string.Empty;
        }

        internal static Fact Create(InstanceDocument parentInstance, XElement factElement, DTS dts)
        {
            Fact factToReturn = null;

            if (!(IsXbrlNamespace(factElement.Name.NamespaceName))
                && !(IsW3Namespace(factElement.Name.NamespaceName))
                && (factElement.NodeType != XmlNodeType.Comment))
            {

                // This fact could be a item, or it could be a tuple. Examine the schemas
                // to find out what we're dealing with.

                Concept matchingElement = null;

                foreach (var currentSchema in dts.TaxonomySchemas)
                {
                    Concept foundElement = currentSchema.GetConceptByName(factElement.Name.NamespaceName, factElement.Name.LocalName);

                    if (foundElement != null)
                    {
                        matchingElement = foundElement;
                        break;
                    }
                }

                if (matchingElement != null)
                {
                    switch (matchingElement.SubstitutionGroup)
                    {
                        case Concept.EnumSubstitutionGroup.Item:
                            factToReturn = new FactItem(parentInstance, factElement, dts);
                            break;
                        case Concept.EnumSubstitutionGroup.Tuple:
                            factToReturn = new FactTuple(parentInstance, factElement, dts);
                            break;
                        default:
                            Concept conceptSubstitutionGroup = dts.GetConceptByName(matchingElement.SchemaElement.SubstitutionGroup.Name);

                            if (conceptSubstitutionGroup != null)
                            {
                                switch (conceptSubstitutionGroup.SubstitutionGroup)
                                {
                                    case Concept.EnumSubstitutionGroup.Item:
                                        factToReturn = new FactItem(parentInstance, factElement, dts);
                                        break;
                                    case Concept.EnumSubstitutionGroup.Tuple:
                                        factToReturn = new FactTuple(parentInstance, factElement, dts);
                                        break;
                                    default:
                                        // This type is unknown, so leave it alone.
                                        break;
                                }
                            }
                                
                        break;
                            
                    }
                }
            }
            return factToReturn;
        }

        /// <summary>
        /// Get the concept from Schema. 
        /// </summary>
        protected Concept GetConceptFromSchema()
        {
            foreach (TaxonomySchema currentSchema in dts.TaxonomySchemas)
            {
                if (currentSchema == null)
                {
                    if (dts.TaxonomySchemas.Count == 0)
                    {
                        string messageFormat = AssemblyResources.GetName("NoSchemasForFragment");
                        StringBuilder messageBuilder = new StringBuilder();
                        messageBuilder.AppendFormat(messageFormat);
                        log.Error("Validation error on fact", new XbrlException(messageBuilder.ToString()));
                        dts.ValidatedInstance = false;
                        break;
                    }
                }

                foreach (Concept currentConcept in currentSchema.Concepts)
                {
                    if (this.Namespace.Equals(currentConcept.SchemaElement.QualifiedName.Namespace) && currentConcept.Name == this.Name)
                    {
                        return currentConcept;
                    }
                }
            } 
 
            if (this.Concept == null)
            {
                string messageFormat = AssemblyResources.GetName("CannotFindFactElementInSchema");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, this.Name, this.parentInstance.FileName);
                log.Error("Validation error on fact", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
                          
            return null;
        }

        /// <summary>
        /// Determines whether or not a namespace URI is hosted by the www.xbrl.org domain.
        /// </summary>
        /// <param name="candidateNamespace">
        /// A namespace URI.
        /// </param>
        /// <returns>
        /// True if the namespace URI is hosted by the www.xbrl.org domain; false otherwise.
        /// </returns>
        private static bool IsXbrlNamespace(string candidateNamespace)
        {
            if (candidateNamespace.Equals("http://www.xbrl.org/2003/linkbase") ||
                candidateNamespace.Equals("http://www.xbrl.org/2003/iso4217") ||
                candidateNamespace.Equals("http://www.xbrl.org/2003/instance"))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Determines whether or not a namespace URI is hosted by the www.w3.org domain.
        /// </summary>
        /// <param name="candidateNamespace">
        /// A namespace URI.
        /// </param>
        /// <returns>
        /// True if the namespace URI is hosted by the www.w3.org domain; false otherwise.
        /// </returns>
        private static bool IsW3Namespace(string candidateNamespace)
        {
            return NamespaceMatchesUri(candidateNamespace, "www.w3.org");
        }

        private static bool NamespaceMatchesUri(string candidateNamespace, string uri)
        {
            candidateNamespace = candidateNamespace.Trim();

            if (candidateNamespace.Length == 0)
            {
                return false;
            }

            Uri namespaceUri = new Uri(candidateNamespace);
            return namespaceUri.Host.ToLower().Equals(uri);
        }
    }
}
